package com.xiaoyung.easyisapisdk.feign.api.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.math.BigDecimal;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONPath;
import com.xiaoyung.easyisapisdk.common.base.Rest;
import com.xiaoyung.easyisapisdk.common.util.ApiException;

public class CallMethodUtil {
	protected static final Logger logger = LoggerFactory.getLogger(CallMethodUtil.class);

	static ConcurrentHashMap<String, Object> serviceCach = new ConcurrentHashMap<>();
	static ConcurrentHashMap<String, Method> methodCach = new ConcurrentHashMap<>();
	
	public static Object getClassInstance(String className) {
		Object instance = serviceCach.get(className);
		if (instance == null) {
			synchronized (serviceCach) {
				if(instance == null) {
					try {
						Class<?> clazz = Class.forName(className);
						Constructor<?> constructor = clazz.getDeclaredConstructor();
						instance = constructor.newInstance();
						serviceCach.put(className, instance);//缓存了实例,防止重复new
					}catch(Exception ex) {
						if(ex instanceof RuntimeException) {
							throw (RuntimeException)ex;
						}else {
							throw new RuntimeException(ex);
						}
					}
				}
			}
		}
		return instance;
	}
	

	public static Method getMethod(String className, String methodName,Integer argsSize) {
		Object instance = getClassInstance(className);
		
		String methodCacheKey = className+"."+methodName+"("+argsSize+")";
		Method findMethod = methodCach.get(methodCacheKey);
		if (findMethod == null) {
			synchronized (methodCach) {
				if(findMethod == null) {
					Method[] methods = instance.getClass().getMethods();
					for(Method m : methods ) {
						if(m.getName().equals(methodName) && m.getParameterCount() == argsSize) {
							findMethod = m;
							break;
						}
					}
					if (findMethod == null) {
						throw new MethodNotFoundException("method不存在 class: "+className+" method:"+methodName+" argsSize:"+argsSize);
						//throw new RuntimeException("method不存在 class: "+className+" method:"+methodName+" argsSize:"+argsSize);
					}
					methodCach.put(methodCacheKey, findMethod);
				}
			}
		}
		return findMethod;
	}

	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	static public Object callClassInstanceMethod(JSONObject param) {
		String className = param.getString("class");
		if(StringUtils.isEmpty(className)) {
			throw new RuntimeException("class为空！");
		}
		String methodName = param.getString("method");
		if(StringUtils.isEmpty(methodName)) {
			throw new RuntimeException("methodName为空！");
		}
		JSONArray args = param.getJSONArray("args");
		Integer argSize = args == null ? 0 : args.size();
		try {
			Object instance = getClassInstance(className);
			Method findMethod = getMethod(className,methodName,args.size());
			
			Object[] objArgs = null;
			if(argSize>0) {
				objArgs = new Object[argSize];
				
				Class<?>[] types = findMethod.getParameterTypes();
				for(int i=0;i<types.length;i++){
					Class<?> clazz = types[i];
					Object value = args.get(i);
					if(value != null) {
						//if(value instanceof JSON && (clazz != JSONObject.class && clazz != JSONArray.class)){//JSONObject JSONArray
						if(value instanceof Map){
							if(!Map.class.isAssignableFrom(clazz)){
								//java bean
								if(!(value instanceof JSONObject || value instanceof JSONArray)) {
									value = JSON.toJSON(value);
								}
								
								value = JSON.to(clazz,value);
							}else {
								//map
								if(value!=null) {
									Map map = clazz==Map.class?new LinkedHashMap<>(): ((Map)clazz.newInstance());
									map.putAll((Map)value);
									value = map;
								}
								
							}
						} else if(value instanceof String && clazz == Date.class){
							value = DateUtils.parseDate(value);
						} else if(clazz == Double.class && !(value instanceof Double) ){
							value = Double.valueOf(value.toString());
						} else if(clazz == Long.class && !(value instanceof Long) ){
							value = Long.valueOf(value.toString());
						} else if(clazz == Integer.class && !(value instanceof Integer) ){
							value = Integer.valueOf(value.toString());
						} else if(clazz == BigDecimal.class && !(value instanceof BigDecimal) ){
							value = new BigDecimal(value.toString());
						}
						/*else if(value instanceof Number && clazz == Integer.class){
							
							}else if(value instanceof Number && clazz == BigDecimal.class){
							
							}*/
						else if(value instanceof Number && clazz == Long.class){
							
						}
						objArgs[i] = value;
					}
					
				}
			}
			//System.out.println(objArgs);
			if(logger.isDebugEnabled()) {
				logger.debug("====call {} {} args: {}",className,methodName,JSON.toJSONString(objArgs));
			}
			//调用实例方法
			//调用无参方法 new Object[0];
			Object result = findMethod.invoke(instance, objArgs == null ? new Object[0] : objArgs);
			//Object obj = org.apache.commons.beanutils.MethodUtils.invokeMethod(instance, methodName, objArgs);
			if(logger.isDebugEnabled()) {
				logger.debug("====call {} {} result: {}", className, methodName, JSON.toJSONString(result));
			}
			return result;
		} catch (InvocationTargetException ex) {//api返回 code!=200
			if(ex.getTargetException() instanceof ApiException) {
				throw (ApiException)ex.getTargetException();	
			}
			throw new RuntimeException(ex);
		} catch (Exception ex) {
			//ex.printStackTrace();
			if(ex instanceof RuntimeException) {
				throw (RuntimeException)ex;
			}else {
				throw new RuntimeException(ex);
			}
		}
	}
	
	public static List<ParamMetaInfo> getMethodParamAnnotation(String className,String methodName) {
		//Method findMethod = getMethod(className,methodName,args.size());
		List<ParamMetaInfo> paths = new ArrayList<>();

		try {
			Class<?> clazz = getClassInstance(className).getClass();
			Method[] methods = clazz.getMethods();
			Method findMethod = null;
			for(Method m : methods ) {
				if(m.getName().equals(methodName)) {// && m.getParameterCount() == argSize
					findMethod = m;
					break;
				}
			}
			if (findMethod == null) {
				throw new MethodNotFoundException("不存在method: "+clazz.getSimpleName()+" "+methodName);
				//throw new RuntimeException("不存在method: "+clazz.getSimpleName()+" "+methodName);// className
			}
			
			// 获取参数
			Parameter[] parameters = findMethod.getParameters();
			// 遍历参数
			for (Parameter parameter : parameters) {
				// 获取注解
				Param annotation = parameter.getAnnotation(Param.class);// ctx all ""
				if (annotation != null) {
					//System.out.println("参数: " + parameter.getName() + ", 注解值: " + annotation.value());
					//parameter.getType()
					ParamMetaInfo meta = new ParamMetaInfo();
					meta.setAnnotation(annotation);
					meta.setParam(parameter);
					paths.add(meta);//StringUtils.isEmpty(value)||value.equals("ctx") || value.equals("all") ?null:value
				}else {
					paths.add(null);//占位
				}
			}
		} catch (Exception e) {
			if(e instanceof RuntimeException) {
				throw (RuntimeException)e;
			}else {
				throw new RuntimeException(e);
			}
		}

		return paths;
	}

	public static Rest<Object> callControllerMethod(String controllerPackagePath,String controller, Map<String,Object> ctx) {

		JSONObject p = new JSONObject();
		String subffix = "Controller";//后缀

		// person => PersonController
		//Integer controllerStrIndex = controller.indexOf(subffix);
		String fullController = Character.toUpperCase(controller.charAt(0))+controller.substring(1);//
		if(!fullController.endsWith(subffix)) {
			fullController += subffix;
		}
		// headers query method url(customUrl) body user
		
		
		Object body = ctx.get("body");
		String method = (String) ctx.get("method");
				
		String className= controllerPackagePath+"."+fullController;
		try {
			List<Object> args = new ArrayList<>();

			List<ParamMetaInfo> paramMetaInfoList = CallMethodUtil.getMethodParamAnnotation(className,method);
			if(paramMetaInfoList.isEmpty()) {
				//无参方法
				args = null;
				//args.add(ctx);
			}else {
				// 1 2 3 
				for (int i = 0; i < paramMetaInfoList.size(); i++) {
					ParamMetaInfo paramMetaInfo = paramMetaInfoList.get(i);
					Param anno =  paramMetaInfo.getAnnotation();
					String path = anno == null ? null : anno.value();
				
					if(!StringUtils.isEmpty(path)) {
						Object param = JSONPath.eval(ctx, path);
						
						Boolean required = anno.required();
						
						String defaultValue = anno.defaultValue();
						Class<?> defaultValueType = paramMetaInfo.getParam().getType();// anno.defaultValueType();
						if (ObjectUtils.isEmpty(param) && !StringUtils.isEmpty(defaultValue)) {
							if (defaultValueType == Integer.class) {
								param = Integer.valueOf(defaultValue);
							} else if (defaultValueType == Long.class) {
								param = Long.valueOf(defaultValue);
							} else if (defaultValueType == Double.class) {
								param = Double.valueOf(defaultValue);
							} else if (defaultValueType == BigDecimal.class) {
								param = new BigDecimal(defaultValue);
							} else if (defaultValueType == Boolean.class) {
								param = Boolean.valueOf(defaultValue);
							} else {
								param = defaultValue;// String
							}
						}
						
						if (ObjectUtils.isEmpty(param) && required) {
							throw new InvalidParameterException(path+" 参数是必须的!");
						}
						
						/*try {
							param = BeanUtils.getProperty(path, className);
						} catch (Exception e) {
							throw new RuntimeException(e);
						}*/
						/*if(param==null) {
							//反射调用方法 需要参数类型 ,因此不能传null
							throw new RuntimeException(className+"."+method+" 根据"+path+"取值为null,方法参数不能为null,请检查是否写错了参数路径,或者没有传入");//+ctx
						}*/
						args.add(param);
					}else {
						args.add(ctx);
					}
				}
			}
			
			p.put("class", className); 
			p.put("method", method);
			p.put("args", args);
			
			if(logger.isDebugEnabled()) {
				logger.debug("\r\n===begin=={}/{} \r\n body参数: {}",controller,method,JSON.toJSON(body));//ctx headers太多了
			}
			Object result = CallMethodUtil.callClassInstanceMethod(p);
			if(logger.isDebugEnabled()) {
				logger.debug("\r\n===end=={}/{} \r\n 返回: {}", controller, method, JSON.toJSONString(result));
			}
			return Rest.success(result);
		}catch(MethodNotFoundException ex) {
			//logger.error("\r\n===url不存在: {}/{} ",controller,method);
			return Rest.error(404, "url不存在"+controller + "/" + method);
		}catch(Exception ex) {// Api
			ex.printStackTrace();
			logger.error("\r\n===end=={}/{} \r\n 出错了: {}", controller, method, ex.getMessage());
			return Rest.error(ex.getMessage());
		}
	}
}
